﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Web;
using System.Web.Mvc;

namespace Xoohoo.Html
{
    public static class SelectExtensions
    {
        #region Static Constructor

        /*
        private static IMethodInvoker s_DropDownListHelperInvoker;
        static SelectExtensions()
        {
            MethodInfo dropDownListHelperMethodInfo = typeof(System.Web.Mvc.Html.SelectExtensions).GetMethod("DropDownListHelper", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            s_DropDownListHelperInvoker = new MethodInvoker(dropDownListHelperMethodInfo);
        }
        */

        #endregion

        #region DropDownList

        //无自定义选项
        public static MvcHtmlString DropDownListFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, TProperty>> expression
            , IEnumerable<SelectListItem> selectList
            )
        {
            return htmlHelper.DropDownListFor(model, expression, selectList,null, null,null);
        }
        public static MvcHtmlString DropDownListFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, TProperty>> expression
            , IEnumerable<SelectListItem> selectList
            , object htmlAttributes)
        {
            return htmlHelper.DropDownListFor(model, expression, selectList,null, null, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
        }
        public static MvcHtmlString DropDownListFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, TProperty>> expression
            , IEnumerable<SelectListItem> selectList
            , IDictionary<string, object> htmlAttributes)
        {
            return htmlHelper.DropDownListFor(model, expression, selectList, null,null, htmlAttributes);
        }

        //自定义选项无value属性
        public static MvcHtmlString DropDownListFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, TProperty>> expression
            , IEnumerable<SelectListItem> selectList
            , string optionLabel)
        {
            return htmlHelper.DropDownListFor(model, expression, selectList, optionLabel,null, null);
        }
        //自定义选项有value属性
        public static MvcHtmlString DropDownListFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, TProperty>> expression
            , IEnumerable<SelectListItem> selectList
            , string optionLabel
            , object optionValue)
        {
            return htmlHelper.DropDownListFor(model
                , expression
                , selectList
                , optionLabel
                , optionValue
                , (IDictionary<string, object>)null);
        }

        //自定义html属性
        public static MvcHtmlString DropDownListFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, TProperty>> expression
            , IEnumerable<SelectListItem> selectList
            , string optionLabel
            , object optionValue
            , object htmlAttributes)
        {
            return htmlHelper.DropDownListFor(model
                , expression
                , selectList
                , optionLabel
                , optionValue
                , HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
        }
        public static MvcHtmlString DropDownListFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, TProperty>> expression
            , IEnumerable<SelectListItem> selectList
            , string optionLabel
            , object optionValue
            , IDictionary<string, object> htmlAttributes)
        {
            if (expression == null)
                throw new ArgumentNullException("expression");
            /*
            return (MvcHtmlString)s_DropDownListHelperInvoker.Invoke(
                null,
                htmlHelper, 
                ExpressionHelper.GetExpressionText(expression), 
                selectList, 
                optionLabel, 
                htmlAttributes);
            */
            ViewDataDictionary<TModel> viewData = new ViewDataDictionary<TModel>(model);
            ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, viewData);
            return DropDownListHelper(htmlHelper
                , metadata
                , ExpressionHelper.GetExpressionText(expression)
                , selectList
                , optionLabel
                , optionValue
                , htmlAttributes);
        }

        #endregion

        private static MvcHtmlString DropDownListHelper<TModel>(this HtmlHelper<TModel> htmlHelper, ModelMetadata metadata, string expression, IEnumerable<SelectListItem> selectList, string optionLabel,object optionValue, IDictionary<string, object> htmlAttributes)
        {
            return SelectInternal(htmlHelper, optionLabel, optionValue, metadata, expression, selectList, false /* allowMultiple */, htmlAttributes);
        }
        private static MvcHtmlString SelectInternal<TModel>(this HtmlHelper<TModel> htmlHelper, string optionLabel,object optionValue, ModelMetadata metadata, string name, IEnumerable<SelectListItem> selectList, bool allowMultiple, IDictionary<string, object> htmlAttributes)
        {
            string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
            if (String.IsNullOrEmpty(fullName))
            {
                throw new ArgumentException("Value cannot be null or empty.", "name");
                //throw new ArgumentException(MvcResources.Common_NullOrEmpty, "name");
            }

            bool usedViewData = false;

            // If we got a null selectList, try to use ViewData to get the list of items.
            if (selectList == null)
            {
                selectList = htmlHelper.GetSelectData(fullName);
                usedViewData = true;
            }

            object defaultValue = (allowMultiple) ? htmlHelper.GetModelStateValue(fullName, typeof(string[])) : htmlHelper.GetModelStateValue(fullName, typeof(string));

            // If we haven't already used ViewData to get the entire list of items then we need to
            // use the ViewData-supplied value before using the parameter-supplied value.
            if (!usedViewData)
            {
                if (defaultValue == null)
                {
                    defaultValue = htmlHelper.ViewData.Eval(fullName);
                }
            }

            if (defaultValue != null)
            {
                IEnumerable defaultValues = (allowMultiple) ? defaultValue as IEnumerable : new[] { defaultValue };
                IEnumerable<string> values = from object value in defaultValues select Convert.ToString(value, CultureInfo.CurrentCulture);
                HashSet<string> selectedValues = new HashSet<string>(values, StringComparer.OrdinalIgnoreCase);
                List<SelectListItem> newSelectList = new List<SelectListItem>();

                foreach (SelectListItem item in selectList)
                {
                    item.Selected = (item.Value != null) ? selectedValues.Contains(item.Value) : selectedValues.Contains(item.Text);
                    newSelectList.Add(item);
                }
                selectList = newSelectList;
            }

            // Convert each ListItem to an <option> tag
            StringBuilder listItemBuilder = new StringBuilder();

            // Make optionLabel the first item that gets rendered.
            if (optionLabel != null)
            {
                listItemBuilder.AppendLine(ListItemToOption(new SelectListItem() { Text = optionLabel, Value = optionValue!=null?optionValue.ToString():String.Empty, Selected = false }));
            }

            foreach (SelectListItem item in selectList)
            {
                listItemBuilder.AppendLine(ListItemToOption(item));
            }

            TagBuilder tagBuilder = new TagBuilder("select")
            {
                InnerHtml = listItemBuilder.ToString()
            };
            tagBuilder.MergeAttributes(htmlAttributes);
            tagBuilder.MergeAttribute("name", fullName, true /* replaceExisting */);
            tagBuilder.GenerateId(fullName);
            if (allowMultiple)
            {
                tagBuilder.MergeAttribute("multiple", "multiple");
            }
            tagBuilder.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(name, metadata));
            // If there are any errors for a named field, we add the css attribute.
            ModelState modelState;
            if (htmlHelper.ViewData.ModelState.TryGetValue(fullName, out modelState))
            {
                if (modelState.Errors.Count > 0)
                {
                    tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName);
                }
            }

            tagBuilder.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(name));

            //return tagBuilder.ToMvcHtmlString(TagRenderMode.Normal);
            return new MvcHtmlString(tagBuilder.ToString(TagRenderMode.Normal));

        }
        internal static string ListItemToOption(SelectListItem item)
        {
            TagBuilder builder = new TagBuilder("option")
            {
                InnerHtml = HttpUtility.HtmlEncode(item.Text)
            };
            if (item.Value != null)
            {
                builder.Attributes["value"] = item.Value;
            }
            if (item.Selected)
            {
                builder.Attributes["selected"] = "selected";
            }
            return builder.ToString(TagRenderMode.Normal);
        }
        private static IEnumerable<SelectListItem> GetSelectData(this HtmlHelper htmlHelper, string name)
        {
            object o = null;
            if (htmlHelper.ViewData != null)
            {
                o = htmlHelper.ViewData.Eval(name);
            }
            if (o == null)
            {
                return Enumerable.Empty<SelectListItem>();
                /*
                throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        "There is no ViewData item of type '{1}' that has the key '{0}'.",
                        //MvcResources.HtmlHelper_MissingSelectData,
                        name,
                        "IEnumerable<SelectListItem>"));
                */
            }
            IEnumerable<SelectListItem> selectList = o as IEnumerable<SelectListItem>;
            if (selectList == null)
            {
                return Enumerable.Empty<SelectListItem>();
                /*
                throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        "The ViewData item that has the key '{0}' is of type '{1}' but must be of type '{2}'.",
                        //MvcResources.HtmlHelper_WrongSelectDataType,
                        name,
                        o.GetType().FullName,
                        "IEnumerable<SelectListItem>"));
                */
            }
            return selectList;
        }
        internal static object GetModelStateValue(this HtmlHelper htmlHelper,string key, Type destinationType)
        {
            ModelState state;
            if (htmlHelper.ViewData.ModelState.TryGetValue(key, out state) && (state.Value != null))
            {
                return state.Value.ConvertTo(destinationType, null);
            }
            return null;
        }

    }
}
